<html>
<head>
<title>Blobs</title>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
<!--

<script type="text/javascript" src="../helpers/glMatrix-0.9.5.min.js"></script>
<script type="text/javascript">
    var gliEmbedDebug = true;
</script>
<script type="text/javascript" src="../core/embed.js"></script>
-->

<script id="blob_shader-vs" type="x-shader/x-vertex">
    attribute vec3 aVertexPosition;
    uniform mat4 uWorldMatrix;
    uniform mat4 uViewProjectionMatrix;
    varying vec2 vTextureCoord;

    void main(void) {
        gl_Position = uViewProjectionMatrix * uWorldMatrix * vec4(aVertexPosition, 1.0);
		vTextureCoord = aVertexPosition.xy;
    }
</script>

<script id="blob_shader-fs" type="x-shader/x-fragment">
    precision mediump float;
    varying vec2 vTextureCoord;
	
    uniform vec3 uColor;
	
    void main(void) {
		float circleCenterLength = length(vTextureCoord); 
		float circleEdgeLength = max(1.0-circleCenterLength,0.0);
		circleEdgeLength = circleEdgeLength*circleEdgeLength;
        gl_FragColor = vec4(circleEdgeLength*uColor,circleEdgeLength);
    }
</script>

<script id="screen_clamp_shader-vs" type="x-shader/x-vertex">
    attribute vec3 aVertexPosition;
    varying vec2 vTextureCoord;
    void main(void) {
        gl_Position = vec4(aVertexPosition.x,aVertexPosition.y,0.0, 1.0);
		vTextureCoord = (aVertexPosition.xy*0.5+0.5);
    }
</script>

<script id="screen_clamp_shader-fs" type="x-shader/x-fragment">
    precision mediump float;
    varying vec2 vTextureCoord;
    uniform sampler2D uSampler;
    void main(void) {
		vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
    	vec3 clampTextureColor = normalize(clamp(textureColor.xyz-0.5,0.0, 1.0));
		
		clampTextureColor = clampTextureColor+textureColor.a*textureColor.a*0.3;
		gl_FragColor = vec4(clampTextureColor.x*vec3(51.0/256.0,68.0/256.0,51.0/256.0)+clampTextureColor.y*vec3(136.0/256.0,170.0/256.0,170.0/256.0)+clampTextureColor.z*vec3(68.0/256.0,119.0/256.0,153.0/256.0),1);
    }
</script>


<script type="text/javascript">

    var gl;
    var blobShaderProgram;
    var screenClampShaderProgram;
    var worldMatrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1];
    var viewProjectionMatrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1];
    var squareVertexPositionBuffer;
    var rttFramebuffer;
    var rttTexture;
	
	var particlePosition = new Array();
	var particleOldPosition = new Array();
	var particleScale = new Array();
	var particleForce = new Array();
	var particleMass = new Array();
	
	
	var particleColors = new Array();
	particleColors[0] = [1,0,0];
	particleColors[1] = [0,1,0];
	particleColors[2] = [0,0,1];
	
	var currentTime = new Date();
	var lastTime = currentTime.getTime();
	
	var numParticles = 256;
	
	var gravity = [-0.3,0];//-9.81];
	
	var mousePos = [0,0];
	var dampening = 0.99;
	var canvas;
    function initGL(canvas) {
        try {
            gl = canvas.getContext("experimental-webgl");
            gl.viewportWidth = canvas.width;
            gl.viewportHeight = canvas.height;
        } catch (e) {
        }
        if (!gl) {
            alert("Could not initialise WebGL, sorry :-(");
        }
    }

    function getShader(gl, id) {
        var shaderScript = document.getElementById(id);
        if (!shaderScript) {
            return null;
        }

        var str = "";
        var k = shaderScript.firstChild;
        while (k) {
            if (k.nodeType == 3) {
                str += k.textContent;
            }
            k = k.nextSibling;
        }

        var shader;
        if (shaderScript.type == "x-shader/x-fragment") {
            shader = gl.createShader(gl.FRAGMENT_SHADER);
        } else if (shaderScript.type == "x-shader/x-vertex") {
            shader = gl.createShader(gl.VERTEX_SHADER);
        } else {
            return null;
        }

        gl.shaderSource(shader, str);
        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            alert(gl.getShaderInfoLog(shader));
            return null;
        }

        return shader;
    }


    function initBlobShader() {
        var fragmentShader = getShader(gl, "blob_shader-fs");
        var vertexShader = getShader(gl, "blob_shader-vs");

        blobShaderProgram = gl.createProgram();
        gl.attachShader(blobShaderProgram, vertexShader);
        gl.attachShader(blobShaderProgram, fragmentShader);
        gl.linkProgram(blobShaderProgram);

        if (!gl.getProgramParameter(blobShaderProgram, gl.LINK_STATUS)) {
            alert("Could not initialise shaders");
        }

        gl.useProgram(blobShaderProgram);

        blobShaderProgram.vertexPositionAttribute = gl.getAttribLocation(blobShaderProgram, "aVertexPosition");
        gl.enableVertexAttribArray(blobShaderProgram.vertexPositionAttribute);

        blobShaderProgram.pMatrixUniform = gl.getUniformLocation(blobShaderProgram, "uViewProjectionMatrix");
        blobShaderProgram.mvMatrixUniform = gl.getUniformLocation(blobShaderProgram, "uWorldMatrix");
		
        blobShaderProgram.colorUniform = gl.getUniformLocation(blobShaderProgram, "uColor");
	}
	
	function initScreenClampShader() {
        var fragmentShader = getShader(gl, "screen_clamp_shader-fs");
        var vertexShader = getShader(gl, "screen_clamp_shader-vs");

        screenClampShaderProgram = gl.createProgram();
        gl.attachShader(screenClampShaderProgram, vertexShader);
        gl.attachShader(screenClampShaderProgram, fragmentShader);
        gl.linkProgram(screenClampShaderProgram);

        if (!gl.getProgramParameter(screenClampShaderProgram, gl.LINK_STATUS)) {
            alert("Could not initialise shaders");
        }

        gl.useProgram(screenClampShaderProgram);

        screenClampShaderProgram.vertexPositionAttribute = gl.getAttribLocation(screenClampShaderProgram, "aVertexPosition");
        gl.enableVertexAttribArray(screenClampShaderProgram.vertexPositionAttribute);

        screenClampShaderProgram.samplerUniform = gl.getUniformLocation(screenClampShaderProgram, "uSampler");
	}

    function initSquareBuffer() {
        squareVertexPositionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
        vertices = [
             1.0,  1.0,  0.0,
            -1.0,  1.0,  0.0,
             1.0, -1.0,  0.0,
            -1.0, -1.0,  0.0
        ];
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
        squareVertexPositionBuffer.itemSize = 3;
        squareVertexPositionBuffer.numItems = 4;
    }

    function initTextureFramebuffer() {
        rttFramebuffer = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
        rttFramebuffer.width = gl.viewportWidth;
        rttFramebuffer.height = gl.viewportHeight;

        rttTexture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, rttTexture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
        gl.generateMipmap(gl.TEXTURE_2D);

		
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, rttFramebuffer.width, rttFramebuffer.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);

        var renderbuffer = gl.createRenderbuffer();
        gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
        gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, rttFramebuffer.width, rttFramebuffer.height);

        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, rttTexture, 0);
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);

        gl.bindTexture(gl.TEXTURE_2D, null);
        gl.bindRenderbuffer(gl.RENDERBUFFER, null);
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    }

	function initScene() {
		for (var i=0; i<numParticles; i++)
		{
			particleOldPosition[i] = [Math.random()*2-1+6,Math.random()*2-1];		
			particlePosition[i] = [particleOldPosition[i][0],particleOldPosition[i][1]];
			particleScale[i] = Math.random()*0.2+0.05;
			particleForce[i] = [0,0];
			particleMass[i] = particleScale[i]*particleScale[i]*Math.PI;
		}
	}
	function updateScene() {
		currentTime = new Date();
		var newTime = currentTime.getTime();
		var iterations = Math.floor((newTime - lastTime)*0.1);
		
		var dt = 0.01;
		lastTime += iterations/0.1; 
		
		
     
	
		var animate = document.getElementById("animateParticles").checked;
		if (animate && !mouseDown)
		{
			if (Math.random()>0.95)
			{
				mousePos[0] = (Math.random()*2-1)*3;
				mousePos[1] = (Math.random()*2-1)*3;
			}
			for (var j=0; j<2; j++)
			{	
				
				mousePos[j] += (Math.random()*2-1)*0.02*iterations;
				mousePos[j] = Math.max(Math.min(mousePos[j], 3.0), -3.0);
			}
		}
	
		for (var itr = 0; itr <iterations; itr++)
		{
			var updateParticles = document.getElementById("updateParticles").checked;
			if (true)//updateParticles) 
			{
				// step
				for (var i=0; i<numParticles; i++)
				{	
					for (var j=0; j<2; j++)
					{	
						var acceleration = particleForce[i][j]/particleMass[i] + gravity[j];
							
						particleForce[i][j] = 0;
						var tmp = particlePosition[i][j];
						particlePosition[i][j] = (1 + dampening) * particlePosition[i][j] - dampening * particleOldPosition[i][j] + acceleration * (dt * dt);
						particleOldPosition[i][j] = tmp;
					}
				}
				
				// bounce
				for (var i=0; i<numParticles; i++)
				{	
					if (particlePosition[i][0] < -1.2)
					{
						particleOldPosition[i][0] = particlePosition[i][0] = particlePosition[i][0]+2.4;
					}
					if (particlePosition[i][1] > 1.0 || particlePosition[i][1] < -1.0)
					{
						particlePosition[i][1] = Math.min(Math.max(particlePosition[i][1], -1.0),1.0);
					}
						/*
					for (var j=0; j<2; j++)
					{			
						if (particlePosition[i][j] > 2.0 || particlePosition[i][j] < -2.0)
						{
							particlePosition[i][j] = Math.min(Math.max(particlePosition[i][j], -2.0),2.0);
						}
					}
					*/
					
				}
				
				// constrain (brute forece) (should itterate over constraints) (shouldnd add force here)
				// y = (1-x)^2 - 0.1
				// x = 1 - sqrt( y + 0.1) [y=0] : x = 0.68378
				var adjust = 1.0-Math.sqrt(0.5);
				for (var i=0; i<numParticles; i++)
				{	
					var connectLen = particleScale[i]*particleScale[i];
					if (!mouseDown && !animate)
					{	
						var diffX = mousePos[0]-particlePosition[i][0];
						var diffY = mousePos[1]-particlePosition[i][1];
						var connectLen = 0.05+particleScale[i];
						var connectInnerLen = connectLen*adjust;
						var sqrLen = diffX*diffX+diffY*diffY;
						if (sqrLen < connectInnerLen*connectInnerLen && sqrLen > 0.001)
						{
							initScene();
						}
					}
					for (var j=0; j<numParticles; j++)
					{
						var diffX = particlePosition[j][0]-particlePosition[i][0];
						var diffY = particlePosition[j][1]-particlePosition[i][1];
						var connectLen = particleScale[j]+particleScale[i];
						var connectInnerLen = connectLen*adjust;
						var sqrLen = diffX*diffX+diffY*diffY;
						
						if (sqrLen < connectLen*connectLen && sqrLen > 0.001)
						{
							var len = Math.sqrt(sqrLen);
							diffX /= len;
							diffY /= len;
							
							
							if (len < connectInnerLen)
							{
							
								var mass = particleMass[j] / ( particleMass[i] + particleMass[j]);
								particlePosition[i][0] -= diffX*(connectInnerLen-len)*mass;
								particlePosition[i][1] -= diffY*(connectInnerLen-len)*mass;
								
								particlePosition[j][0] += diffX*(connectInnerLen-len)*(1.0-mass);
								particlePosition[j][1] += diffY*(connectInnerLen-len)*(1.0-mass);
							}
						}
					}
				}
				
				for (var i=0; i<numParticles; i++)
				{	
					var Itype = Math.floor(i/numParticles*3);
					var connectLen = particleScale[i]*particleScale[i];
					for (var j=0; j<numParticles; j++)
					{
						var Jtype = Math.floor(j/numParticles*3);
						
						var diffX = particlePosition[j][0]-particlePosition[i][0];
						var diffY = particlePosition[j][1]-particlePosition[i][1];
						var connectLen = particleScale[j]+particleScale[i];
						var sqrLen = diffX*diffX+diffY*diffY;
						if (sqrLen < connectLen*connectLen && sqrLen > 0.001)
						{
							var len = Math.sqrt(sqrLen);
							diffX /= len;
							diffY /= len;
							
							if (updateParticles && Itype == 0)
							{
								particleForce[i][0] -= diffX*(connectLen-len)*6;
								particleForce[i][1] -= diffY*(connectLen-len)*6;
							}
							
							if (Jtype == Itype)
							{
								particleForce[i][0] += diffX*(connectLen-len)*1;
								particleForce[i][1] += diffY*(connectLen-len)*1;
							}
						}
					}
				}
				for (var i=0; i<numParticles; i++)
				{	
					var diffX = mousePos[0]-particlePosition[i][0];
					var diffY = mousePos[1]-particlePosition[i][1];
					var sqrLen = diffX*diffX+diffY*diffY;
					if (sqrLen > 0.001 && sqrLen < 1 && (mouseDown || animate))
					{
						var len = Math.sqrt(sqrLen);
						diffX /= len;
						diffY /= len;
				 
						particleForce[i][0] += diffX*6;
						particleForce[i][1] += diffY*6;
					}
				}
							
			}
		}
	}
    
	function drawScene() {
		
		gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
		gl.viewport(0, 0, rttFramebuffer.width, rttFramebuffer.height);
		
		gl.clearColor(0.0, 0.0, 0.0, 0.0);
		gl.clearDepth(1.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
		
		gl.blendFunc(gl.ONE, gl.ONE);
		gl.enable(gl.BLEND);
		gl.disable(gl.DEPTH_TEST);
     
	    gl.useProgram(blobShaderProgram);
		gl.uniformMatrix4fv(blobShaderProgram.pMatrixUniform, false, viewProjectionMatrix);
        
		gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
		gl.vertexAttribPointer(blobShaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
		var numParticleSection = Math.floor(numParticles/3);
		for (var j=0; j<3; j++)
		{
			gl.uniform3fv(blobShaderProgram.colorUniform, particleColors[j]);
			var start = numParticleSection*j;
			for (var i=start; i<start+numParticleSection; i++)
			{
				worldMatrix = [particleScale[i],0,0,0, 0,particleScale[i],0,0, 0,0,1,0, particlePosition[i][0],particlePosition[i][1],0,1];
				gl.uniformMatrix4fv(blobShaderProgram.mvMatrixUniform, false, worldMatrix);
				gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
			}
			
		}
		
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
		gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
        gl.disable(gl.BLEND);
		
		// hmm... need to look into this
		gl.bindTexture(gl.TEXTURE_2D, rttTexture);
		gl.generateMipmap(gl.TEXTURE_2D);
		gl.bindTexture(gl.TEXTURE_2D, null);
		
        gl.useProgram(screenClampShaderProgram);
		gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, rttTexture);
        gl.uniform1i(screenClampShaderProgram.samplerUniform, 0);
		
		gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
        gl.bindTexture(gl.TEXTURE_2D, null);
		
		// mouse
		
		gl.enable(gl.BLEND);
	    gl.useProgram(blobShaderProgram);
		gl.uniformMatrix4fv(blobShaderProgram.pMatrixUniform, false, viewProjectionMatrix);
		{
			gl.uniform3fv(blobShaderProgram.colorUniform, [1,0,0]);
			worldMatrix = [0.1,0,0,0, 0,0.1,0,0, 0,0,1,0, mousePos[0],mousePos[1],0,1];
			gl.uniformMatrix4fv(blobShaderProgram.mvMatrixUniform, false, worldMatrix);
			gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
		}
		
		
    }
	
	var mouseDown = false;
    function handleMouseDown(event) {
        mouseDown = true;
		handleMouseMove(event);
		
    }


    function handleMouseUp(event) {
        mouseDown = false;
    }


    function handleMouseMove(event) {
        
		mousePos[0] = (event.offsetX/canvas.width*2-1);
		mousePos[1] = (-(event.offsetY/canvas.height*2-1));
	}
	function handleButtonClick(event) {
        initScene();
	}

    function tick() {
        requestAnimFrame(tick);
		updateScene();
        drawScene();
    }
	
	function hexToRGBA(hexVal)
	{
		var res = new Array();
		if (hexVal[0] != '#')
			alert('non hex');
		else
		{
			for (var i=0; i<hexVal.length/2-1; i++)
			{
				var p = '0x'+hexVal[i*2+1]+hexVal[i*2+2];
				res[i] = parseInt(p);
			}
		}
		return res;
	}

    function webGLStart() {
        canvas = document.getElementById("blob-canvas");
		
        canvas.onmousedown = handleMouseDown;
        canvas.onmouseup = handleMouseUp;
        document.onmousemove = handleMouseMove;
		
		document.getElementById("initParticles").onclick = handleButtonClick;
		
        initGL(canvas);
        initTextureFramebuffer();
        initBlobShader();
        initScreenClampShader();
        initSquareBuffer();
		initScene();
		
		tick();       
    }
	
	window.requestAnimFrame = (function() {
	  return window.requestAnimationFrame ||
			 window.webkitRequestAnimationFrame ||
			 window.mozRequestAnimationFrame ||
			 window.oRequestAnimationFrame ||
			 window.msRequestAnimationFrame ||
			 function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
			   window.setTimeout(callback, 1000/60);
			 };
	})();


</script>
</head>
<body onload="webGLStart();">
    <input type="checkbox" id="updateParticles" />spread red<br/>
    <input type="checkbox" id="animateParticles" />animate<br/>
    <input type="button" id="initParticles" name="initParticles"/><br/>
    <canvas id="blob-canvas" style="border: none;" width="512" height="512"></canvas>
</body>
</html>
